/*
 * test.c
 *
 * Generates a test pattern for display.
 *
 * Copyright (c) 2010, Alan Green.
 * Contactable at <firstname>.<lastname>@gmail.com
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1.  Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *  2.  Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * ===============================================
 */

#include <stdio.h>
#include "display.h"
#include "util.h"
#include "text_paint.h"
#include "test.h"

static void diagonal_solid_line(uint8_t);
static void diagonal_regular_line(uint8_t);

static void diagonal_solid_line(uint8_t sync_count) {
    fill_display_ram(0xff);
    line_logic = diagonal_regular_line;
}

static void diagonal_regular_line(uint8_t sync_count) {
    fill_display_ram(0x00);
    display_ram[sync_count / 8] = BITS[sync_count & 0x7];
    display_ram[0] |= 0x80;
    display_ram[DISPLAY_RAM_LEN - 1] = sync_count;
    if (sync_count == LAST_TEXT_SAFE_LINE - 1) {
        line_logic = diagonal_solid_line;
    }
}

void test_diagonal_function(void) {
    line_logic = diagonal_solid_line;
}

// Sets up a bouncing ball
static const int16_t min_y = 1;
static const int16_t max_y = LAST_TEXT_SAFE_LINE - 1;

static const int16_t min_x = 1;
static const int16_t max_x = DISPLAY_RAM_LEN * 8 - 2;

static int16_t ball_x = 160;
static int16_t ball_y = 167;
static int16_t ball_dx = -2;
static int16_t ball_dy = -3;

// Fill dispslay ram with given value

void border_line(uint8_t sync_count) {
    if (sync_count == 0) {
        fill_display_ram(0x04);
    } else if (sync_count == 1 || sync_count == LAST_TEXT_SAFE_LINE) {
        fill_display_ram(0xff);
    } else {
        fill_display_ram(0x00);
        display_ram[0] = 0x80;
        display_ram[DISPLAY_RAM_LEN - 1] = 0x01;
    }
}

void test_border_function(void) {
    line_logic = border_line;
}

// prototypes for line logic functions
static void bounce_boundary_line(uint8_t sync_count);
static void bounce_regular_line(uint8_t sync_count);

static void bounce_boundary_line(uint8_t sync_count) {
    fill_display_ram(0xff);
    line_logic = bounce_regular_line;
}

// A fast implementation of  display_ram[x >> 3] |= BITS[x & 0x7];
static void draw_dot_at(uint16_t x) {

    register uint8_t tmp2;
    register uint16_t ram_ptr;
    asm volatile ("\n"
            // get lowest 3 bits and lookup BITS table. Result in tmp2
            "    mov   %[tmp2], %A[x]            \n"
            "    andi  %[tmp2], 0x7              \n"
            "    movw  %[ram_ptr], %[BITS]       \n"
            "    add   %A[ram_ptr], %[tmp2]      \n"
            "    adc   %B[ram_ptr], __zero_reg__ \n"
            "    ld    %[tmp2], X                \n"
            // Shift a copy of X 3 bits right.
            // Assumes Max of 1 significant bit in high byte.
            "    movw  %[ram_ptr], %[x]          \n"
            "    asr   %B[ram_ptr]               \n"
            "    ror   %A[ram_ptr]               \n"
            "    lsr   %A[ram_ptr]               \n"
            "    lsr   %A[ram_ptr]               \n"
            // Add in address of display ram to get byte address in ram_ptr
            "    add   %A[ram_ptr],%A[d_ram]     \n"
            "    adc   %B[ram_ptr],%B[d_ram]     \n"
            // Get current value of display ram, OR with value in tmp2,
            // and store it
            "    ld    __tmp_reg__, %a[ram_ptr]  \n"
            "    or    __tmp_reg__, %[tmp2]      \n"
            "    st    %a[ram_ptr], r0           \n"
            : // I/O
            [tmp2] "=&r" (tmp2),
            [ram_ptr] "=&x" (ram_ptr)
            : // inputs
            [d_ram] "r" (display_ram),
            [x] "r" (x),
            [BITS] "r" (BITS)
    );
}

static void bounce_regular_line(uint8_t sync_count) {
    fill_display_ram(0x00);
    display_ram[0] |= 0x80;
    display_ram[DISPLAY_RAM_LEN - 1] |= 0x01;

    int8_t yd = ball_y - sync_count;
    if (yd == 0) {
        draw_dot_at(ball_x - 2);
        draw_dot_at(ball_x + 2);
    } else if (yd >= -1 && yd <= 1) {
        draw_dot_at(ball_x - 1);
        draw_dot_at(ball_x + 1);
    } else if (yd == -2 || yd == 2) {
        draw_dot_at(ball_x);
    }

    if (sync_count == LAST_TEXT_SAFE_LINE - 1) {
        line_logic = bounce_boundary_line;
    }
}

void bounce_function(void) {
    int16_t new_ball_x = ball_x + ball_dx;
    if (new_ball_x > max_x || new_ball_x < min_x) {
        ball_dx = -ball_dx;
        new_ball_x = ball_x + ball_dx;
    } else {
        ball_x = new_ball_x;
    }
    int16_t new_ball_y = ball_y + ball_dy;
    if (new_ball_y > max_y || new_ball_y < min_y) {
        ball_dy = -ball_dy;
        new_ball_y = ball_y + ball_dy;
    } else {
        ball_y = new_ball_y;
    }
    line_logic = bounce_boundary_line;
}

//
// Show text
char msg_0[] = "Hello, World!";
char msg_1[] = "12345678901234567890";
char msg_2[] = "12345678901234567890123"; // 23 chars is the limit
char msg_3[] = "12345678901234567890123";
static char * msg_table[] = { msg_0, msg_1, msg_2, msg_3 };

static uint16_t frame_counter = 0;
static uint8_t msg_counter = 0;
static char * curr_msg = msg_0;

static void text_line_function(uint8_t sync_count) {
    uint8_t y = sync_count - 44;
    if (y < 8) {
        paint_chars(curr_msg, 0, y);
    } else {
        fill_display_ram(0x00);
    }
}

void text_function(void) {
    frame_counter++;
    if (frame_counter == 50) {
        frame_counter = 0;
        msg_counter++;
        msg_counter &= 3;
        curr_msg = msg_table[msg_counter];
    }
    line_logic = text_line_function;
}

// Input device testing
static uint8_t b_values[40];
static char pot_fmt[] PROGMEM = "Pot value: %4d";
static char pot_value[23];
void input_test_line_function(uint8_t line) {
    if (line < 8) {
        paint_chars("Button presses", 0, line);
    } else if (line >= 12 && line < 15) {
        display_ram[0] = b_values[0];
        display_ram[1] = b_values[1];
        display_ram[2] = b_values[2];
        display_ram[3] = b_values[3];
        display_ram[4] = b_values[4];
        display_ram[5] = b_values[5];
        display_ram[6] = b_values[6];
        display_ram[7] = b_values[7];
        display_ram[8] = b_values[8];
        display_ram[9] = b_values[9];
        display_ram[10] = b_values[10];
        display_ram[11] = b_values[11];
        display_ram[12] = b_values[12];
        display_ram[13] = b_values[13];
        display_ram[14] = b_values[14];
        display_ram[15] = b_values[15];
        display_ram[16] = b_values[16];
        display_ram[17] = b_values[17];
        display_ram[18] = b_values[18];
        display_ram[19] = b_values[19];
        display_ram[20] = b_values[20];
        display_ram[21] = b_values[21];
        display_ram[22] = b_values[22];
        display_ram[23] = b_values[23];
        display_ram[24] = b_values[24];
        display_ram[25] = b_values[25];
        display_ram[26] = b_values[26];
        display_ram[27] = b_values[27];
        display_ram[28] = b_values[28];
        display_ram[29] = b_values[29];
        display_ram[30] = b_values[30];
        display_ram[31] = b_values[31];
        display_ram[32] = b_values[32];
        display_ram[33] = b_values[33];
        display_ram[34] = b_values[34];
        display_ram[35] = b_values[35];
        display_ram[36] = b_values[36];
        display_ram[37] = b_values[37];
        display_ram[38] = b_values[38];
        display_ram[39] = b_values[39];
    } else if (line >= 100 && line < 108) {
        paint_chars(pot_value, 0, line - 100);
    }    else {
        fill_display_ram(0x00);
    }
}

void input_test_function(void) {
    uint8_t top_bit = (PINC & _BV(PC0)) ? 0x00 : 0x80;
    for (uint8_t i = 0; i < 40; i++) {
        uint8_t next_top_bit = b_values[i] & 0x01 ? 0x80 : 0x00;
        b_values[i] = top_bit | (b_values[i] >> 1);
        top_bit = next_top_bit;
    }

    snprintf_P(pot_value, 23, pot_fmt, read_pot(PORT5));

}

void init_input_test(void) {
    frame_logic = input_test_function;
    line_logic = input_test_line_function;
}
